/* 
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System;
using Lucene.Net.Analysis.Tokenattributes;
using Lucene.Net.Documents;
using IndexOutput = Lucene.Net.Store.IndexOutput;
using UnicodeUtil = Lucene.Net.Util.UnicodeUtil;

namespace Lucene.Net.Index
{
    
    sealed class TermVectorsTermsWriterPerField:TermsHashConsumerPerField
    {
        
        internal TermVectorsTermsWriterPerThread perThread;
        internal TermsHashPerField termsHashPerField;
        internal TermVectorsTermsWriter termsWriter;
        internal FieldInfo fieldInfo;
        internal DocumentsWriter.DocState docState;
        internal FieldInvertState fieldState;
        
        internal bool doVectors;
        internal bool doVectorPositions;
        internal bool doVectorOffsets;
        
        internal int maxNumPostings;
        internal IOffsetAttribute offsetAttribute = null;
        
        public TermVectorsTermsWriterPerField(TermsHashPerField termsHashPerField, TermVectorsTermsWriterPerThread perThread, FieldInfo fieldInfo)
        {
            this.termsHashPerField = termsHashPerField;
            this.perThread = perThread;
            this.termsWriter = perThread.termsWriter;
            this.fieldInfo = fieldInfo;
            docState = termsHashPerField.docState;
            fieldState = termsHashPerField.fieldState;
        }
        
        internal override int GetStreamCount()
        {
            return 2;
        }
        
        internal override bool Start(IFieldable[] fields, int count)
        {
            doVectors = false;
            doVectorPositions = false;
            doVectorOffsets = false;
            
            for (int i = 0; i < count; i++)
            {
                IFieldable field = fields[i];
                if (field.IsIndexed && field.IsTermVectorStored)
                {
                    doVectors = true;
                    doVectorPositions |= field.IsStorePositionWithTermVector;
                    doVectorOffsets |= field.IsStoreOffsetWithTermVector;
                }
            }
            
            if (doVectors)
            {
                if (perThread.doc == null)
                {
                    perThread.doc = termsWriter.GetPerDoc();
                    perThread.doc.docID = docState.docID;
                    System.Diagnostics.Debug.Assert(perThread.doc.numVectorFields == 0);
                    System.Diagnostics.Debug.Assert(0 == perThread.doc.perDocTvf.Length);
                    System.Diagnostics.Debug.Assert(0 == perThread.doc.perDocTvf.FilePointer);
                }

                System.Diagnostics.Debug.Assert(perThread.doc.docID == docState.docID);
                if (termsHashPerField.numPostings != 0)
                {
                    // Only necessary if previous doc hit a
                    // non-aborting exception while writing vectors in
                    // this field:
                    termsHashPerField.Reset();
                    perThread.termsHashPerThread.Reset(false);
                }
            }
            
            // TODO: only if needed for performance
            //perThread.postingsCount = 0;
            
            return doVectors;
        }
        
        public void  Abort()
        {
        }
        
        /// <summary>Called once per field per document if term vectors
        /// are enabled, to write the vectors to
        /// RAMOutputStream, which is then quickly flushed to
        /// the real term vectors files in the Directory. 
        /// </summary>
        internal override void  Finish()
        {
            
            System.Diagnostics.Debug.Assert(docState.TestPoint("TermVectorsTermsWriterPerField.finish start"));
            
            int numPostings = termsHashPerField.numPostings;
            
            System.Diagnostics.Debug.Assert(numPostings >= 0);
            
            if (!doVectors || numPostings == 0)
                return ;
            
            if (numPostings > maxNumPostings)
                maxNumPostings = numPostings;
            
            IndexOutput tvf = perThread.doc.perDocTvf;
            
            // This is called once, after inverting all occurences
            // of a given field in the doc.  At this point we flush
            // our hash into the DocWriter.
            
            System.Diagnostics.Debug.Assert(fieldInfo.storeTermVector);
            System.Diagnostics.Debug.Assert(perThread.VectorFieldsInOrder(fieldInfo));
            
            perThread.doc.AddField(termsHashPerField.fieldInfo.number);
            
            RawPostingList[] postings = termsHashPerField.SortPostings();
            
            tvf.WriteVInt(numPostings);
            byte bits = (byte) (0x0);
            if (doVectorPositions)
                bits |= TermVectorsReader.STORE_POSITIONS_WITH_TERMVECTOR;
            if (doVectorOffsets)
                bits |= TermVectorsReader.STORE_OFFSET_WITH_TERMVECTOR;
            tvf.WriteByte(bits);
            
            int encoderUpto = 0;
            int lastTermBytesCount = 0;
            
            ByteSliceReader reader = perThread.vectorSliceReader;
            char[][] charBuffers = perThread.termsHashPerThread.charPool.buffers;
            for (int j = 0; j < numPostings; j++)
            {
                TermVectorsTermsWriter.PostingList posting = (TermVectorsTermsWriter.PostingList) postings[j];
                int freq = posting.freq;
                
                char[] text2 = charBuffers[posting.textStart >> DocumentsWriter.CHAR_BLOCK_SHIFT];
                int start2 = posting.textStart & DocumentsWriter.CHAR_BLOCK_MASK;
                
                // We swap between two encoders to save copying
                // last Term's byte array
                UnicodeUtil.UTF8Result utf8Result = perThread.utf8Results[encoderUpto];
                
                // TODO: we could do this incrementally
                UnicodeUtil.UTF16toUTF8(text2, start2, utf8Result);
                int termBytesCount = utf8Result.length;
                
                // TODO: UTF16toUTF8 could tell us this prefix
                // Compute common prefix between last term and
                // this term
                int prefix = 0;
                if (j > 0)
                {
                    byte[] lastTermBytes = perThread.utf8Results[1 - encoderUpto].result;
                    byte[] termBytes = perThread.utf8Results[encoderUpto].result;
                    while (prefix < lastTermBytesCount && prefix < termBytesCount)
                    {
                        if (lastTermBytes[prefix] != termBytes[prefix])
                            break;
                        prefix++;
                    }
                }
                encoderUpto = 1 - encoderUpto;
                lastTermBytesCount = termBytesCount;
                
                int suffix = termBytesCount - prefix;
                tvf.WriteVInt(prefix);
                tvf.WriteVInt(suffix);
                tvf.WriteBytes(utf8Result.result, prefix, suffix);
                tvf.WriteVInt(freq);
                
                if (doVectorPositions)
                {
                    termsHashPerField.InitReader(reader, posting, 0);
                    reader.WriteTo(tvf);
                }
                
                if (doVectorOffsets)
                {
                    termsHashPerField.InitReader(reader, posting, 1);
                    reader.WriteTo(tvf);
                }
            }
            
            termsHashPerField.Reset();

            // NOTE: we clear, per-field, at the thread level,
            // because term vectors fully write themselves on each
            // field; this saves RAM (eg if large doc has two large
            // fields w/ term vectors on) because we recycle/reuse
            // all RAM after each field:
            perThread.termsHashPerThread.Reset(false);
        }
        
        internal void  ShrinkHash()
        {
            termsHashPerField.ShrinkHash(maxNumPostings);
            maxNumPostings = 0;
        }
        
        internal override void  Start(IFieldable f)
        {
            if (doVectorOffsets)
            {
                offsetAttribute = fieldState.attributeSource.AddAttribute<IOffsetAttribute>();
            }
            else
            {
                offsetAttribute = null;
            }
        }
        
        internal override void  NewTerm(RawPostingList p0)
        {
            
            System.Diagnostics.Debug.Assert(docState.TestPoint("TermVectorsTermsWriterPerField.newTerm start"));
            
            TermVectorsTermsWriter.PostingList p = (TermVectorsTermsWriter.PostingList) p0;
            
            p.freq = 1;
            
            if (doVectorOffsets)
            {
                int startOffset = fieldState.offset + offsetAttribute.StartOffset; ;
                int endOffset = fieldState.offset + offsetAttribute.EndOffset;
                
                termsHashPerField.WriteVInt(1, startOffset);
                termsHashPerField.WriteVInt(1, endOffset - startOffset);
                p.lastOffset = endOffset;
            }
            
            if (doVectorPositions)
            {
                termsHashPerField.WriteVInt(0, fieldState.position);
                p.lastPosition = fieldState.position;
            }
        }
        
        internal override void  AddTerm(RawPostingList p0)
        {
            
            System.Diagnostics.Debug.Assert(docState.TestPoint("TermVectorsTermsWriterPerField.addTerm start"));
            
            TermVectorsTermsWriter.PostingList p = (TermVectorsTermsWriter.PostingList) p0;
            p.freq++;
            
            if (doVectorOffsets)
            {
                int startOffset = fieldState.offset + offsetAttribute.StartOffset; ;
                int endOffset = fieldState.offset + offsetAttribute.EndOffset;
                
                termsHashPerField.WriteVInt(1, startOffset - p.lastOffset);
                termsHashPerField.WriteVInt(1, endOffset - startOffset);
                p.lastOffset = endOffset;
            }
            
            if (doVectorPositions)
            {
                termsHashPerField.WriteVInt(0, fieldState.position - p.lastPosition);
                p.lastPosition = fieldState.position;
            }
        }
        
        internal override void  SkippingLongTerm()
        {
        }
    }
}